package image.synth;

import image.ImageGroup;
import image.ps.PixelShader;
import java.util.LinkedList;
import static math.math.*;
import static math.vec2i.*;
import math.vec2i;

public class Synthesizer
{
    
    private static final long SEED_DELTA_X = -125983281L;
    
    private static final long SEED_DELTA_Y = 947238975L;
    
    
    public ImageGroup rTarget;
    
    public long mSeed;
    
    public vec2i mTileSize;
    
    
    public Synthesizer()
    {
        this.rTarget = null;
        this.mSeed = 0L;
        this.mTileSize = new vec2i(32); // Default to 32x32 tiles.
    }
    
    
    public void computeImage(
            PixelShader aPS,
            vec2i aImageStart, 
            vec2i aImageSize)
    {
        vec2i numTiles = div(aImageSize, this.mTileSize); // number of regular-sized tiles.
        
        LinkedList<TileSynth> tiles = new LinkedList<>();
        
        vec2i index = new vec2i();
        for (index.y = 0; index.y < numTiles.y; index.y++)
        {
            for (index.x = 0; index.x < numTiles.x; index.x++)
            {
                long tileSeed = (
                    this.mSeed + 
                    (SEED_DELTA_X * index.x) + 
                    (SEED_DELTA_Y * index.y)
                );
                vec2i tileStart = mul(index, this.mTileSize);
                TileSynth tile = new TileSynth(tileStart, this.mTileSize, aImageSize, tileSeed);
                tile.rTarget = this.rTarget;
                tile.rPS = aPS.copy();
                tiles.addLast(tile);
            }
        }
        
        vec2i regTileSpan = mul(numTiles, this.mTileSize);
        vec2i remPixels = sub(aImageSize, regTileSpan);
        
        boolean hasRemX = remPixels.x > 0;
        boolean hasRemY = remPixels.y > 0;
        if (hasRemX)
        {
            vec2i tileSize = new vec2i(remPixels.x, this.mTileSize.y); // size of remX tiles.
            
            vec2i indRem = new vec2i(numTiles.x, 0);
            for (indRem.y = 0; indRem.y < numTiles.y; indRem.y++)
            {
                long tileSeed = (
                    this.mSeed + 
                    (SEED_DELTA_X * indRem.x) + 
                    (SEED_DELTA_Y * indRem.y)
                );
                vec2i tileStart = mul(indRem, this.mTileSize);
                TileSynth tileRemX = new TileSynth(tileStart, tileSize, aImageSize, tileSeed);
                tileRemX.rTarget = this.rTarget;
                tileRemX.rPS = aPS.copy();
                tiles.addLast(tileRemX);
            }
        }
        if (hasRemY)
        {
            vec2i tileSize = new vec2i(this.mTileSize.x, remPixels.y); // size of remY tiles.
            
            vec2i indRem = new vec2i(0, numTiles.y);
            for (indRem.x = 0; indRem.x < numTiles.x; indRem.x++)
            {
                long tileSeed = (
                    this.mSeed + 
                    (SEED_DELTA_X * indRem.x) + 
                    (SEED_DELTA_Y * indRem.y)
                );
                vec2i tileStart = mul(indRem, this.mTileSize);
                TileSynth tileRemY = new TileSynth(tileStart, tileSize, aImageSize, tileSeed);
                tileRemY.rTarget = this.rTarget;
                tileRemY.rPS = aPS.copy();
                tiles.addLast(tileRemY);
            }
        }
        if (hasRemX || hasRemY)
        {
            vec2i tileSize = remPixels; // size of remXY tile.
            
            long tileSeed = (
                this.mSeed + 
                (SEED_DELTA_X * numTiles.x) + 
                (SEED_DELTA_Y * numTiles.y)
            );
            vec2i tileStart = mul(numTiles, this.mTileSize);
            TileSynth tileRemXY = new TileSynth(tileStart, tileSize, aImageSize, tileSeed);
            tileRemXY.rTarget = this.rTarget;
            tileRemXY.rPS = aPS.copy();
            tiles.addLast(tileRemXY);
        }
        
        Thread[] threads = new Thread[tiles.size()];
        int i = 0;
        while (!tiles.isEmpty())
        {
            TileSynth tile = tiles.removeFirst();
            Thread t = new Thread(tile);
            threads[i++] = t;
            t.start();
        }
        
        int lastRunning = threads.length;
        boolean joining = true;
        while (joining)
        {
            int nRunning = 0;
            for (Thread t : threads)
            {
                if (t.isAlive())
                {
                    nRunning++;
                }
            }
            if (nRunning == 0)
            {
                joining = false;
            }
            if (nRunning != lastRunning)
            {
                int diff = lastRunning - nRunning;
                lastRunning = nRunning;
                
                double probOutput = clampd(20.0 * diff * (1.0 / threads.length), 0.0, 1.0);
                if (rand1f() < probOutput)
                {
                    double pct = 100.0 - (100.0 * nRunning) / threads.length;
                    String sPct = String.format("%.2f", pct);
                    System.out.println("% " + sPct);
                }
            }
        }
        
    }
    
}
